home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 20
/
Aminet 20 (1997)(GTI - Schatztruhe)[!][Aug 1997].iso
/
Aminet
/
dev
/
misc
/
RenderLib.lha
/
RenderLib
/
Doc
/
Tutorial
< prev
next >
Wrap
Text File
|
1997-06-10
|
19KB
|
538 lines
tutorial
-----------------------------
this tutorial intends to generally explain basic terms of image
processing and to instruct the programming of render.library.
introduction
-----------------------------
render.library provides low-level image processing. there are no
functions implemented that interact with higher-level resources,
such as rastports or datatypes. you may call render.library an
image processing kernel.
there's no reason for hesitation, though. i hope this tutorial
will help you to find the thread to image processing and
conversion. as you will see, the functions provided with
render.library are very easy to use, once you've understood the
basic terms, and most functions provide much more power and
functionality than it might appear at first sight.
philosophy
-----------------------------
function interfaces are kept lean and logical. first of all i
focused on using as few definitions as possible. you will notice
that you can use the majority of render.library's functions
without setting up a single structure. each function was reduced
to an absolute minimum of mandatory parameters. any argument that
in some way could be assigned a default value to has been made
optional and was banned to the taglists.
render.library is layouted quasi object-oriented. everything is
kept in black boxes, and objects heavily interact internally, only
you don't know anything about the chemistry. many functions may be
interpreted as constructors, destructors and methods, only the
interfaces are implemented procedurally. there is nothing
available for misguiding, insane assumptions.
overview
-----------------------------
render.library covers the following domains:
> planar to chunky conversion
and vice versa. the functions in this domain are considered
low-level, and they do not interact with higher-level
graphics.library instances such as rastports. you have to
take good care not to play havoc with non-standard bitmaps.
please read the appropriate autodoc chapters very carefully.
- Planar2ChunkyA()
- Chunky2BitMapA()
> chunky to truecolor conversion
this is a simple task, and render.library serves it with
high speed and flexibility. Amiga-specific modes (HAM6 and
HAM8) are correctly handled even with horizontal offsets.
- Chunky2RGBA()
> palettes
render.library uses a special type of color-lookup tables,
called 'palettes'. a render.library palette is much more
than a mere table of RGB values. all that nasty stuff is
hidden from you throughout these functions:
- CreatePaletteA()
- ImportPaletteA()
- ExportPaletteA()
- SortPaletteA()
> histograms
histograms are in most cases not more than a means to an
end. they are required for color reduction, statistics, and
for certain low-level functions. render.library supports
'real' truecolor histograms with up to 24bit resolution.
this feature is rarely found (and rarely needed, but
sometimes it cannot be dispensed).
- CreateHistogramA()
- AddChunkyImageA()
- AddRGBImageA()
- AddRGB()
- AddHistogram()
- CountRGB()
- QueryHistogram()
> color reduction (quantization)
this task is everything but trivial. render.library
uses a sophisticated algorythm that can stand the test
with the most-elaborated implementations on the Amiga
platform.
- ExtractPaletteA()
> truecolor to chunky conversion (rendering)
this task was the initial idea for render.library, and
render.library deserves its name. just have a look at all
those possible taglist arguments for
- RenderA()
> chunky to chunky conversion
this is similar to RenderA(), only that it accepts
chunky bytes as input.
- ConvertChunkyA()
- CreatePenTableA()
> mapping
in addition to those fully-featured rendering and
chunky-conversion functions, render.library offers a
low-level conversion class called mapping-engine.
- CreateMapEngineA()
- MapChunkyArrayA()
- MapRGBArrayA()
> scaling
render.library provides an own scaling class. its instances
are called scaling-engines. the performance of
non-interpolating scaling-engines comes close to CopyMem().
Scaling-engines may be passed to RenderA() and
ConvertChunkyA(), allowing scaling and rendering in a
single pass.
- CreateScaleEngineA()
- ScaleA()
- ScaleOrdinate()
> alpha-channel
this is a trivial task with render.library and can be
achieved with these functions:
- InsertAlphaChannelA()
- ExtractAlphaChannelA()
- ApplyAlphaChannelA()
- MixRGBArrayA()
> memory management
render.library features a lean and simple, but yet
effective memory management system throughout these
functions:
- CreateRMHandlerA()
- AllocRenderMem()
- AllocRenderVec()
data types
-----------------------------
what kind of data can be processed with render.library?
currently, there are the following formats available:
> PIXFMT_0RGB_32 (ULONG)
longword truecolor pixels, with the sequence
0x00rrggbb
the upmost byte is not defined, and in most cases may
contain alpha-channel information.
> PIXFMT_CHUNKY_CLUT (UBYTE)
chunky-byte pixels with color-lookup table. a pixel's
actual color is not defined throughout this format.
therefore you need a palette for most operations with
arrays of chunky bytes. usually, the byte value
acts directly as an index to the color-lookup table.
for certain operations, an additional specification is required
for the access to a color-lookup table:
> COLORMODE_CLUT
this mode defines 'normal' color lookup (see above). one
byte directly acts as an index to a color-lookup table.
> COLORMODE_HAM6
this mode defines HAM6 color lookup. one byte does not
necessarily reference a palette index.
> COLORMODE_HAM6
this mode defines HAM8 color lookup. one byte does not
necessarily reference a palette index.
remember that COLORMODE_HAM6 and COLORMODE_HAM8 still apply to
chunky pixels, although HAM is an Amiga specific format that was
(unfortunately) never defined for chunky bytes, but rather for
planar data ('bitplanes').
memory management
-----------------------------
since image processing is in general quite hungry for memory
resources, render.library offers a both simple and effective
custom memory management.
you don't need to use it, but it may help you to make things
easier and faster. under normal circumstances, you probably want
to set up a pooled memory environment. pooled memory helps to
avoid memory fragmentation, and it preserves the system's public
memory lists from too much stressing. create a pooled memhandler
as follows:
memhandler = CreateRMHandler(RND_MemType, RMHTYPE_POOLED,
TAG_DONE);
* note: pooled memory is not available prior to exec v39. if
not available, just create a 'standard' memory handler with
RMHTYPE_PUBLIC - the default value. if your project won't
include many large images, palettes and histograms, you
might as well create a 'standard' memory handler.
you might even live without a memhandler, since the memhandler
argument is always optional - public memory will be used in
this case. nevertheless, i recommend to use a memhandler. it
helps you to easily upgrade your code to a more sophisticated
memory management when necessary. for the use with dynamic
histograms (see next section), a pooled memory handler is
highly advised.
you may also set up a memory manager that operates in a static
block of memory. this is similar to the memory management of
programs like AdPro. they grab a large block of memory at
startup and allocate all intermediate buffers from this memory
area. example:
memhandler = CreateRMHandler(RND_MemType, RMHTYPE_PRIVATE,
RND_MemBlock, my_memblock,
RND_MemSize, my_memblock_size, TAG_DONE);
* note: private memory management is less flexible than a
pooled memory environment. once the static buffer is full or
becomes fragmented, further allocations will fail. v39 exec
pools will automatically grow and shrink to the required size.
not only render.library functions may profit from a memhandler.
your application has access to it throughout these functions:
mem = AllocRenderMem(memhandler, size);
FreeRenderMem(memhandler, mem, size);
mem = AllocRenderVec(memhandler, size);
FreeRenderVec(mem);
the main idea is to allow your application and render.library to
share a particular memory pool. this will keep the whole memory
management lean and effective.
histograms
-----------------------------
a histogram is an object that holds color information. it
maintains a counter for each color.
many functions are related to histograms. their creation is
quite simple:
histogram = CreateHistogram(RND_RMHandler, memhandler,
RND_HSType, HSTYPE_15BIT_TURBO,
TAG_DONE);
render.library offers two different histogram schemes: the
_TURBO types are provided with tables, the 'normal' types use
digital trees. tabular histograms are very fast, but their
memory consumption is determined according to 2^bits x 4
bytes. with other words, a 12bit histogram is very small (8192
bytes), but a 24bit histogram would require 64mb of memory. it
would be nonsense to create a 24bit tabular histogram anyway,
since most images do not contain all possible RGB values. that's
why _TURBO histograms are limited to 12, 15, and 18 bit
resolution.
tree histograms are more flexible. their memory consumption is
not predictable, because they are created dynamically. a
drawback is that every single RGB entry in a tree histogram
requires 20 bytes of memory. the tree type allows you to create
'real' histograms with full 24bit accuracy. by the way, this is
the only way to evaluate the effective number of different
colors in an image. (most image processing applications operate
with 15bit histograms and tell you nonsense about the number of
colors found.)
* a 15 bit _TURBO histogram is the best choice in most cases.
as mentioned before, most image processing applications use
this resolution. render.library histograms are interpolated.
this reasonably compensates the loss of information.
now that we've created a histogram, what are we going to do with
it? first of all, we have to load it with color information.
success = AddRGBImage(histogram, rgb_array,
640, 480, NULL);
in this example, a 640×480 truecolor image is added to the
histogram. you may of course add as many pictures as you like,
e.g. in order to record all the single frames of an animation.
it's also possible to directly add chunky images to a histogram:
success = AddChunkyImage(histogram, chunky_array, 640, 480,
palette, NULL);
(as you can see, chunky images require a palette. palettes will
be covered by the next chapter.)
* note: the return value <success> is not boolean, it's
defined with constants. you must check for ADDH_SUCCESS, since
adding data to histograms may fail at any time. your histogram
won't be corrupted in that case, but if you don't check the
return value, you can't depend on finding your histogram in a
state that correctly represents the data you've added.
you can find out about certain histogram parameters with the
QueryHistogram() function. for instance
number_of_pixels = QueryHistogram(histogram, RND_NumPixels);
number_of_colors = QueryHistogram(histogram, RND_NumColors);
another way to access histograms is the CountRGB() function. it
returns the number of pixels recorded for a specific RGB value:
pixels_represented = CountRGB(histogram, 0x00rrggbb);
* note: according to current definitions, histograms can hold
the information for 2^32 (4,28 billions) pixels. as long as no
overflow occurs to a single entry inside the histogram, they
may even contain 2^32 different colors. further
histogram-related processing (e.g. quantization) is defined
for 2^32 pixels, though.
palettes
-----------------------------
most functions dealing with chunky pixels require color-lookup
tables. in the context of render.library, they are called
'palettes'. palettes cover many technical details internally.
a palette is created this way:
palette = CreatePalette(NULL);
this sets up a palette of 15bit resolution. these 15bit suffice
in most situations, and they are taken into account only if some
kind of rendering or mapping takes place. more control can be
obtained via taglist arguments:
palette = CreatePalette(RND_HSType, HSTYPE_12BIT, TAG_DONE);
as you can see, a palette's resolution is specified analogously
to a histogram's resolution.
* note: palettes do not differenciate between _TURBO and
non-TURBO types.
in addition to that, you can supply CreatePalette() with a
memhandler (which is not a bad idea at all):
palette = CreatePalette(RND_RMHandler, memhandler, TAG_DONE);
after all, what you get is just an empty palette ready for being
loaded with colors. by default, ImportPaletteA() handles
0x00rrggbb color entries. you may as well import LoadRGB32-alike
and LoadRGB4-type tables.
ULONG colortable[5] =
{0xff0088, 0x007532, 0x435278, 0xffffff, 0x000000};
ImportPalette(palette, colortable, 5, NULL);
you may import colors mulitple times and specify
offsets:
ImportPalette(palette, colortable1, 10, NULL);
ImportPalette(palette, colortable2, 20, RND_NewPalette, FALSE,
RND_FirstColor, 10, TAG_DONE);
with RND_NewPalette set to FALSE, ImportPaletteA() merges
color entries to a palette. the RND_FirstColor tag specifies
the offset where to add new colors. the total number of
colors in a palette is automatically maintained and updated.
the only restriction is that you are neither allowed to
import more than 256 colors, nor to import beyond the 256th
entry. the counterpart of ImportPaletteA() is
ExportPaletteA().
another way to load a palette with colors is via
ExtractPaletteA() - refer to the next section.
quantization
-----------------------------
i've got you here, right? quantization ('color reduction') is
extremely easy with render.library. you just have to supply a
source histogram and a destination palette.
success = ExtractPalette(histogram, palette, numcolors, NULL);
this is the basic function call. it will extract the given
number of colors from the histogram and insert it into the
palette. refer to the autodocs for further details.
rendering
-----------------------------
now for the climax of it all and to what gave render.library its
name. let's transform a RGB array to chunky pixels:
success = Render(rgbarray, width, height, chunkyarray,
palette, tags);
there are many tags available, for dithering, offsets, secondary
conversions, HAM mode support, scaling, callback hooks, and
more. the same applies to the chunky-byte equivalent:
success = ConvertChunky(sourcearray, sourcepalette,
width, height, destarray, destpalette, tags);
no need to mention that these functions are quite fast. if
they're still not fast enough for your purposes, refer to the
next section.
mapping
-----------------------------
you are disappointed by the speed of Render(), because it is
only several times faster than any other software on the Amiga?
then you have to enter the wonderful realm of mapping-engines.
mapping-engines focus on three aspects: a) speed, b) speed, c)
speed.
* mapping-engines depend on a destination palette. The
contents of that destination palette may change, but it must
not be deleted prior to dependent mapping-engines.
the mapping-engine may additionally depend on a histogram
containing the color information of those pixels that are
going to be mapped. this histogram has to be of a _TURBO
type of the same resolution as the palette. if there's a
histogram available that fits these specifications, you
are strongly recommended to use it¹. this improves speed
remarkably.
mapengine = CreateMapEngine(destpalette, NULL);
mapengine =
CreateMapEngine(destpalette, RND_Histogram, histogram,
TAG_DONE);
now there are two ways of using a mapping-engine. the
preferrable way is to pass it to RenderA() or ConvertChunkyA()
with the RND_MapEngine tag argument. if you can manage without
dithering, scaling, callback hooks and alike, you might as well
use it with these functions:
MapRGBArray(mapengine, rgbarray, width, height, chunkyarray,
tags);
MapChunkyArray(mapengine, sourcearray, sourcepalette, width,
height, destarray, tags);
the above functions have got very low overhead and provide
rather primitive data transfer schemes. MapRGBArrayA() comes
close to physical bus performance (with totally 4 memory
accesses per pixel) and renders almost real-time on fast
processors.
* a drawback with MapRGBArrayA() is that your RGB pixels may not
contain a trailing alphachannel-byte. it must by set to zero,
otherwise you must use RenderA() instead.
a call to a function with a mapping-engine can be quite slow
when invoked for the first time (or whenever the destination
palette changed). the mapping-engine's internal buffers have
to be (re-)constructed then. that's where the optional
histogram gets into action: only those RGB values actually
appearing in the histogram are updated. if no histogram was
specified with CreateMapEngineA(), a mapping-table will be
calculated for the whole RGB space. the palette's resolution
is a significant factor then: a 12bit RGB space holds 4096
colors, a 15bit RGB space has got 32768 colors, and a 18bit
RGB space contains 262144 colors. you better know what you're
doing when creating a mapping-engine for a 18bit palette and
do not specify a histogram.
* ¹note: nothing serious happens if the supplied histogram
does not correctly represent the data you convert with a
mapping-engine. however, colors that are not part of the
histogram will be mapped to nonsense pixels. unfortunately
this applies to conversions with dithering. you should
disable dithering when histogram-related mapping-engines are
passed to RenderA() or ConvertChunkyA().